/*
 * MIT License
 *
 * Copyright (c) 2023-present, tangli
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package tony.test.crypto.asymmetric

import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertTrue
import tony.core.exception.ApiException
import tony.crypto.asymmetric.Sm2
import java.security.KeyPair

/**
 * SM2 非对称加密算法测试类
 *
 * 测试 SM2 算法的具体实现，包括密钥生成、加密解密、数字签名等功能
 *
 * @author tangli
 * @date 2025/01/22 20:00
 */
@DisplayName("SM2 非对称加密测试")
class Sm2Test {

    @Nested
    @DisplayName("SM2 密钥生成测试")
    inner class Sm2KeyGenerationTest {

        @Test
        @DisplayName("generateKeyPair(): 应该生成有效的 SM2 密钥对")
        fun testGenerateSm2KeyPair() {
            // When
            val keyPair: KeyPair = Sm2.generateKeyPair()

            // Then
            assertNotNull(keyPair, "SM2 密钥对不应为空")
            assertNotNull(keyPair.public, "SM2 公钥不应为空")
            assertNotNull(keyPair.private, "SM2 私钥不应为空")

            // 验证密钥算法
            assertEquals("EC", keyPair.public.algorithm, "公钥算法应该是 EC")
            assertEquals("EC", keyPair.private.algorithm, "私钥算法应该是 EC")
        }

        @Test
        @DisplayName("generateKeyPair(): 生成的密钥对应该可用于加密解密")
        fun testGeneratedKeyPairForEncryption() {
            // Given
            val keyPair: KeyPair = Sm2.generateKeyPair()
            val testData = "SM2 测试数据".toByteArray(Charsets.UTF_8)

            // When
            val encrypted: ByteArray = Sm2.encrypt(testData, keyPair.public.encoded)
            val decrypted: ByteArray = Sm2.decrypt(encrypted, keyPair.private.encoded)

            // Then
            assertArrayEquals(testData, decrypted, "使用生成的密钥对加密解密应该成功")
        }

        @Test
        @DisplayName("generateKeyPair(): 生成的密钥对应该可用于签名验证")
        fun testGeneratedKeyPairForSignature() {
            // Given
            val keyPair: KeyPair = Sm2.generateKeyPair()
            val testData = "SM2 签名测试数据".toByteArray(Charsets.UTF_8)

            // When
            val signature: ByteArray = Sm2.sign(testData, keyPair.private.encoded)
            val isValid: Boolean = Sm2.verify(testData, signature, keyPair.public.encoded)

            // Then
            assertTrue(isValid, "使用生成的密钥对签名验证应该成功")
        }
    }

    @Nested
    @DisplayName("SM2 加密解密测试")
    inner class Sm2EncryptDecryptTest {

        private val keyPair: KeyPair = Sm2.generateKeyPair()
        private val publicKey: ByteArray = keyPair.public.encoded
        private val privateKey: ByteArray = keyPair.private.encoded

        @Test
        @DisplayName("encrypt(): 应该正确加密中文数据")
        fun testEncryptChineseText() {
            // Given
            val chineseText = "这是一段中文测试数据，包含特殊字符：！@#￥%……&*（）".toByteArray(Charsets.UTF_8)

            // When
            val encrypted: ByteArray = Sm2.encrypt(chineseText, publicKey)
            val decrypted: ByteArray = Sm2.decrypt(encrypted, privateKey)

            // Then
            assertArrayEquals(chineseText, decrypted, "中文数据加密解密应该正确")
        }

        @Test
        @DisplayName("encrypt(): 应该正确加密二进制数据")
        fun testEncryptBinaryData() {
            // Given
            val binaryData = byteArrayOf(0x00, 0x01, 0x02, 0xFF.toByte(), 0xFE.toByte(), 0xFD.toByte())

            // When
            val encrypted: ByteArray = Sm2.encrypt(binaryData, publicKey)
            val decrypted: ByteArray = Sm2.decrypt(encrypted, privateKey)

            // Then
            assertArrayEquals(binaryData, decrypted, "二进制数据加密解密应该正确")
        }

        @Test
        @DisplayName("encrypt(): 空数据应该抛出 IllegalArgumentException")
        fun testEncryptEmptyDataThrowsException() {
            // Given
            val emptyData = ByteArray(0)

            // When & Then
            val exception = assertThrows<IllegalArgumentException> {
                Sm2.encrypt(emptyData, publicKey)
            }
            assertEquals("Source data cannot be empty", exception.message)
        }

        @Test
        @DisplayName("decrypt(): 空数据应该抛出 IllegalArgumentException")
        fun testDecryptEmptyDataThrowsException() {
            // Given
            val emptyData = ByteArray(0)

            // When & Then
            val exception = assertThrows<IllegalArgumentException> {
                Sm2.decrypt(emptyData, privateKey)
            }
            assertEquals("Source data cannot be empty", exception.message)
        }

        @Test
        @DisplayName("encrypt(): 无效公钥应该抛出 ApiException")
        fun testEncryptWithInvalidPublicKey() {
            // Given
            val testData = "test".toByteArray()
            val invalidPublicKey = "invalid_key".toByteArray()

            // When & Then
            assertThrows<ApiException> {
                Sm2.encrypt(testData, invalidPublicKey)
            }
        }

        @Test
        @DisplayName("decrypt(): 无效私钥应该抛出 ApiException")
        fun testDecryptWithInvalidPrivateKey() {
            // Given
            val testData = "test".toByteArray()
            val encrypted = Sm2.encrypt(testData, publicKey)
            val invalidPrivateKey = "invalid_key".toByteArray()

            // When & Then
            assertThrows<ApiException> {
                Sm2.decrypt(encrypted, invalidPrivateKey)
            }
        }

        @Test
        @DisplayName("decrypt(): 无效密文应该抛出 ApiException")
        fun testDecryptInvalidCiphertext() {
            // Given
            val invalidCiphertext = "invalid_ciphertext".toByteArray()

            // When & Then
            assertThrows<ApiException> {
                Sm2.decrypt(invalidCiphertext, privateKey)
            }
        }
    }

    @Nested
    @DisplayName("SM2 数字签名测试")
    inner class Sm2SignatureTest {

        private val keyPair: KeyPair = Sm2.generateKeyPair()
        private val publicKey: ByteArray = keyPair.public.encoded
        private val privateKey: ByteArray = keyPair.private.encoded

        @Test
        @DisplayName("sign(): 应该正确签名中文数据")
        fun testSignChineseText() {
            // Given
            val chineseText = "SM2数字签名测试：中文内容".toByteArray(Charsets.UTF_8)

            // When
            val signature: ByteArray = Sm2.sign(chineseText, privateKey)
            val isValid: Boolean = Sm2.verify(chineseText, signature, publicKey)

            // Then
            assertTrue(signature.isNotEmpty(), "签名不应为空")
            assertTrue(isValid, "中文数据签名验证应该成功")
        }

        @Test
        @DisplayName("sign(): 空数据应该返回空签名")
        fun testSignEmptyData() {
            // Given
            val emptyData = ByteArray(0)

            // When
            val signature: ByteArray = Sm2.sign(emptyData, privateKey)

            // Then
            assertTrue(signature.isEmpty(), "空数据签名应该为空")
        }

        @Test
        @DisplayName("verify(): 空数据和空签名应该验证成功")
        fun testVerifyEmptyDataAndSignature() {
            // Given
            val emptyData = ByteArray(0)
            val emptySignature = ByteArray(0)

            // When
            val isValid: Boolean = Sm2.verify(emptyData, emptySignature, publicKey)

            // Then
            assertTrue(isValid, "空数据和空签名验证应该成功")
        }

        @Test
        @DisplayName("verify(): 只有数据为空应该验证失败")
        fun testVerifyOnlyDataEmpty() {
            // Given
            val emptyData = ByteArray(0)
            val nonEmptySignature = "signature".toByteArray()

            // When
            val isValid: Boolean = Sm2.verify(emptyData, nonEmptySignature, publicKey)

            // Then
            assertFalse(isValid, "只有数据为空时验证应该失败")
        }

        @Test
        @DisplayName("verify(): 只有签名为空应该验证失败")
        fun testVerifyOnlySignatureEmpty() {
            // Given
            val nonEmptyData = "data".toByteArray()
            val emptySignature = ByteArray(0)

            // When
            val isValid: Boolean = Sm2.verify(nonEmptyData, emptySignature, publicKey)

            // Then
            assertFalse(isValid, "只有签名为空时验证应该失败")
        }

        @Test
        @DisplayName("verify(): 被篡改的数据应该验证失败")
        fun testVerifyTamperedData() {
            // Given
            val originalData = "原始数据".toByteArray(Charsets.UTF_8)
            val tamperedData = "被篡改的数据".toByteArray(Charsets.UTF_8)
            val signature = Sm2.sign(originalData, privateKey)

            // When
            val isValid: Boolean = Sm2.verify(tamperedData, signature, publicKey)

            // Then
            assertFalse(isValid, "被篡改数据的签名验证应该失败")
        }

        @Test
        @DisplayName("verify(): 无效公钥应该验证失败")
        fun testVerifyWithInvalidPublicKey() {
            // Given
            val testData = "test data".toByteArray()
            val signature = Sm2.sign(testData, privateKey)
            val invalidPublicKey = "invalid_key".toByteArray()

            // When
            val isValid: Boolean = Sm2.verify(testData, signature, invalidPublicKey)

            // Then
            assertFalse(isValid, "使用无效公钥验证应该失败")
        }

        @Test
        @DisplayName("sign(): 无效私钥应该抛出 ApiException")
        fun testSignWithInvalidPrivateKey() {
            // Given
            val testData = "test data".toByteArray()
            val invalidPrivateKey = "invalid_key".toByteArray()

            // When & Then
            assertThrows<ApiException> {
                Sm2.sign(testData, invalidPrivateKey)
            }
        }
    }

    @Nested
    @DisplayName("SM2 性能和稳定性测试")
    inner class Sm2PerformanceTest {

        @Test
        @DisplayName("encrypt() 和 decrypt(): 多次操作应该保持一致性")
        fun testMultipleEncryptDecryptConsistency() {
            // Given
            val keyPair = Sm2.generateKeyPair()
            val testData = "一致性测试数据".toByteArray(Charsets.UTF_8)

            // When & Then
            repeat(10) {
                val encrypted = Sm2.encrypt(testData, keyPair.public.encoded)
                val decrypted = Sm2.decrypt(encrypted, keyPair.private.encoded)
                assertArrayEquals(testData, decrypted, "第 ${it + 1} 次加密解密应该一致")
            }
        }

        @Test
        @DisplayName("sign() 和 verify(): 多次操作应该保持一致性")
        fun testMultipleSignVerifyConsistency() {
            // Given
            val keyPair = Sm2.generateKeyPair()
            val testData = "签名一致性测试数据".toByteArray(Charsets.UTF_8)

            // When & Then
            repeat(10) {
                val signature = Sm2.sign(testData, keyPair.private.encoded)
                val isValid = Sm2.verify(testData, signature, keyPair.public.encoded)
                assertTrue(isValid, "第 ${it + 1} 次签名验证应该成功")
            }
        }

        @Test
        @DisplayName("generateKeyPair(): 大量密钥生成应该稳定")
        fun testMassiveKeyGeneration() {
            // When & Then
            repeat(5) {
                val keyPair = Sm2.generateKeyPair()
                assertNotNull(keyPair.public, "第 ${it + 1} 个密钥对的公钥不应为空")
                assertNotNull(keyPair.private, "第 ${it + 1} 个密钥对的私钥不应为空")
            }
        }
    }
}
